var Emitter = require('emitter');

function cloneArray(arr) {
  var _arr = [];
  for (var i = 0, len = arr.length; i < len; i++) {
    _arr[i] = arr[i];
  }
  return _arr;
}

function equals(v1, v2) {
  if (Math.abs(v1 - v2) <= 20) {
    return true;
  }
}

function delegate(agent, func) {
  return function() {
    return agent[func].apply(agent, arguments);
  };
};

function HeadTracker(interval) {
  Emitter.call(this);
  //frames per second
  this.interval = interval || 1000 / 20;
  this.ctx = null;
  this.intervalId = 0;
  this.video = document.createElement('video');
  //draw video on the backgroundCanvas, and call getImageData to retrieve data to proceed
  this.bc = document.createElement('canvas');
  //whether the camera is ready for taking pictures
  this.ready = false;
  this.width = 480;
  this.height = 360;
  this.bc.width = this.width;
  this.bc.height = this.height;
  this.legalCounts = [1,2,3,4,5,6,7];
  this.blockLength = 5;
  this.bctx = this.bc.getContext('2d');
  this.previousData = [];
  this.previousProceededData = [];
}

HeadTracker.prototype = new Emitter();

HeadTracker.prototype.init = function() {
  var self = this;
  navigator.webkitGetUserMedia({video: true}, function(stream) {
    var src = webkitURL.createObjectURL(stream);
    self.video.src = src;
    self.video.play();
    self.ready = true;
    self.emit('ready');
  });
};

HeadTracker.prototype.start = function() {
  if (!this.ready || this.intervalId != 0) {
    return;
  }
  var self = this;
  this.intervalId = setInterval(function() {
    self.step();
  }, self.interval);
};

HeadTracker.prototype.stop = function() {
  if (this.intervalId) {
    clearInterval(this.intervalId);
  }
  this.intervalId = 0;
};

HeadTracker.prototype.step = function() {
  var width = this.width;
  var height = this.height;
  this.bctx.drawImage(this.video, 0, 0, width, height);
  var idata = this.bctx.getImageData(0, 0, width, height);
  var data = idata.data;
    
  this.xorPoints(data);
  this.excludeIllegalPoints(data);
  this.excludeLighterPoints(data);
  // this.excludeNoise(data);
  // this.stablize(data);
  /*
  * [todos]
  * emit head moving events
  * head is moving: left, right, up, down, near, far
  */
  if (this.ctx) {
    this.ctx.putImageData(idata, 0, 0);
  }
};

HeadTracker.prototype.fillPoint = function(data, i, color) {
  data[i] = color.r;
  data[i + 1] = color.g;
  data[i + 2] = color.b;
};

HeadTracker.prototype.isPointWhite = function(data, i) {
  if (data[i] == null) {
    return true;
  }
  if (data[i] === 255 && data[i + 1] === 255 && data[i + 2] === 255) {
    return true;
  }
};

HeadTracker.prototype.isPointLegal = function(data, x, y) {
  var width = this.width;
  var count = 0;
  var points = [
    ((y - 1) * width + x) * 4,
    (y * width + x - 1) * 4,
    ((y - 1) * width + x - 1) * 4,
    ((y - 1) * width + x + 1) * 4,
    ((y + 1) * width + x - 1) * 4,
    ((y + 1) * width + x + 1) * 4,
    (y * width + x + 1) * 4,
    ((y + 1) * width + x) * 4
  ];
  for (var i = 0; i < points.length; i++) {
    if (!this.isPointWhite(data, points[i])) {
      count++;
    }
  }
  if (this.legalCounts.indexOf(count) === -1) {
    return false;
  } else {
    return true;
  }
};

//retrieve pixels that are different from the previous frame
HeadTracker.prototype.xorPoints = function(data) {
  var previousData = cloneArray(data);
  if (this.previousData.length) {
    for (var i = 0, len = data.length; i < len; i += 4) {
      if (equals(data[i], this.previousData[i]) && equals(data[i + 1], this.previousData[i + 1]) && equals(data[i + 2], this.previousData[i + 2])) {
        this.fillPoint(data, i, {
          r: 255,
          g: 255,
          b: 255
        });
      }
    }
  }
  this.previousData = previousData;
};

/*
* Each pixel is surrounded by eight other pixels,
* in order to retrieve lines from pictures,
* I need to exclude illegal pixels which are either too isolated or too crowded.
*/

HeadTracker.prototype.excludeIllegalPoints = function(data) {
  var _data = cloneArray(data);
  var width = this.width;
  var height = this.height;
  for (var x = 0; x < width; x++) {
    for (var y = 0; y < height; y++) {
      if (this.isPointWhite(_data, (y * width + x) * 4)) {
        continue;
      }
      if (!this.isPointLegal(_data, x, y)) {
        this.fillPoint(data, (y * width + x) * 4, {
          r: 255,
          g: 255,
          b: 255
        });
      }
    }
  }
};

//exclude pixels that are lighter than average
HeadTracker.prototype.excludeLighterPoints = function(data) {
  var sum = [0, 0, 0];
  var count = 0;
  for (var i = 0, len = data.length; i < len; i += 4) {
    if (!this.isPointWhite(data, i)) {
      sum[0] += data[i];
      sum[1] += data[i + 1];
      sum[2] += data[i + 2];
      count += 1;
    }
  }
  sum[0] /= count;
  sum[1] /= count;
  sum[2] /= count;
  for (var i = 0, len = data.length; i < len; i += 4) {
    if (this.isPointWhite(data, i)) {
      continue;
    }
    if (data[i] > sum[0] && data[i + 1] > sum[1] && data[i + 2] > sum[2]) {
      this.fillPoint(data, i, {
        r: 255,
        g: 255,
        b: 255
      });
    }
  }
};

HeadTracker.prototype.excludeNoise = function(data) {
  /*
  * todos:
  * 1. dramatic overall light level change
  * 2. device and/or background shaking
  * 3. exclude background from human head using pixel density check
  */
  var xBlockCount = this.width / this.blockLength;
  var yBlockCount = this.height / this.blockLength;
  var max = 0;
  var sum = 0;
  var densities = [];
  for (var i = 0; i < yBlockCount; i++) {
    densities[i] = [];
  }
  for (var x = 0; x < xBlockCount; x += 1) {
    for (var y = 0; y < yBlockCount; y += 1) {
      var density = this.calculateDensity(data, x, y);
      sum += density;
      if (density > max) {
        max = density;
      }
      densities[y][x] = density;
    }
  }
  var ave = sum / (xBlockCount * yBlockCount);
  for (var x = 0; x < xBlockCount; x += 1) {
    for (var y = 0; y < yBlockCount; y += 1) {
      if (densities[y][x] < ave) {
      // if (densities[y][x] < max * 0.2) {
        this.fillBlock(data, x, y, {
          r: 255,
          g: 255,
          b: 255
        });
      } else {
        this.fillBlock(data, x, y, {
          r: 0,
          g: 0,
          b: 0
        });
      }
    }
  }
};

HeadTracker.prototype.calculateDensity = function(data, xIndex, yIndex) {
  var count = 0;
  var lenX = (xIndex + 1) * this.blockLength;
  var lenY = (yIndex + 1) * this.blockLength;
  for (var x = xIndex * this.blockLength; x < lenX; x += 1) {
    for (var y = yIndex * this.blockLength; y < lenY; y += 1) {
      if (!this.isPointWhite(data, (y * this.width + x) * 4)) {
        count += 1;
      }
    }
  }
  return count / (this.blockLength * this.blockLength);
};

HeadTracker.prototype.fillBlock = function(data, xIndex, yIndex, color) {
  var lenX = (xIndex + 1) * this.blockLength;
  var lenY = (yIndex + 1) * this.blockLength;
  for (var x = xIndex * this.blockLength; x < lenX; x += 1) {
    for (var y = yIndex * this.blockLength; y < lenY; y += 1) {
      this.fillPoint(data, (y * this.width + x) * 4, color);
    }
  }
};

HeadTracker.prototype.stablize = function(data) {
  /*
  * todos:
  * stablize result using human head movement prediction
  */
};

module.exports = HeadTracker;